#include "FPCH.h"

#include "FCharMap.h"

#include "FLibrary.h"

namespace Font
{

	CharMap::CharMap(Library* a_Library)
	{
		m_Library = a_Library;

		m_RangeStart = 0xFFFFFFFF;
		m_RangeEnd = 0;

		m_BinFirst = m_BinLatest = NULL;
		m_BinAdded = 0;

		SetDefault(L'?');
	}

	CharMap::CharMap(Library* a_Library, const tb::StringBase<wchar_t>& a_Range, wchar_t a_Default /*= L' '*/)
	{
		m_Library = a_Library;

		m_CodesDirty = true;
		m_CodeHighest = -1;
		m_Default = a_Default;

		m_PagesTotal = 10;
		m_Pages = new Page*[m_PagesTotal];
		for (unsigned int i = 0; i < m_PagesTotal; i++) { m_Pages[i] = NULL; }

		m_RangeStart = 0xFFFFFFFF;
		m_RangeEnd = 0;

		Load(a_Range);

		SetDefault(L'?');
	}

	CharMap::CharMap(Library* a_Library, unsigned int a_RangeStart, unsigned int a_RangeEnd, wchar_t a_Default /*= L' '*/)
	{
		m_Library = a_Library;

		m_CodesDirty = true;
		m_CodeHighest = -1;
		m_Default = a_Default;

		m_PagesTotal = 10;
		m_Pages = new Page*[m_PagesTotal];
		for (unsigned int i = 0; i < m_PagesTotal; i++) { m_Pages[i] = NULL; }

		m_RangeStart = 0xFFFFFFFF;
		m_RangeEnd = 0;

		m_BinFirst = m_BinLatest = NULL;
	
		Load(a_RangeStart, a_RangeEnd);

		SetDefault(L'?');
	}

	CharMap::~CharMap()
	{
		delete [] m_Pages;
	}

	bool CharMap::AddRange(unsigned int a_Identifier)
	{
		UnicodeRange* entry = m_Library->GetUnicodeRangeByIdentifier(a_Identifier);
		if (!entry) { return false; }

		_AddRange(entry);

		return true;
	}

	void CharMap::AddRange(UnicodeRange* a_Range)
	{
		_AddRange(a_Range);
	}

	bool CharMap::AddRangeCharacters(const tb::StringBase<wchar_t>& a_String)
	{
		for (int j = 0; j < a_String.GetLength(); j++)
		{
			unsigned int ident = (unsigned int)a_String[j];

			bool found = false;
			for (size_t i = 0; i < m_Ranges.size(); i++)
			{
				UnicodeRange* curr = m_Ranges[i];
				if (ident >= curr->start && ident < curr->end)
				{
					found = true;
					break;
				}
			}

			if (!found)
			{
				UnicodeRange* entry = m_Library->GetUnicodeRangeByCharacter(ident);
				if (!entry)
				{
					return false;
				}

				_AddRange(entry);
			}
		}

		return true;
	}

	void CharMap::AddKnownRange(unsigned int a_RangeStart, unsigned int a_RangeEnd)
	{
		UnicodeRange* entry = new UnicodeRange;
		entry->start = a_RangeStart;
		entry->end = a_RangeEnd;
		entry->next = NULL;
		_AddRange(entry);

		_AddBinRange(a_RangeStart, a_RangeEnd);

		Compile();
	}

	bool CharMap::Load(const tb::StringBase<wchar_t>& a_Range)
	{
		m_Codes = Parse(a_Range);
		if (m_Codes.GetLength() <= 0) { return false; }

		Compile();

		return true;
	}

	bool CharMap::Load(unsigned int a_RangeStart, unsigned int a_RangeEnd)
	{
		unsigned int start, end;
		if (a_RangeStart > a_RangeEnd)
		{
			start = a_RangeEnd;
			end = a_RangeStart;
		}
		else
		{
			start = a_RangeStart;
			end = a_RangeEnd;
		}

		unsigned int code_size = end - start + 1;
		if (code_size == 0) { return false; }

		m_Codes.Clear();
		m_Codes.Resize(code_size);
		for (unsigned int i = start; i <= end; i++)
		{
			m_Codes[i] = i;
		}

		Compile();

		return true;
	}

	void CharMap::Compile()
	{
		m_CodeHighest = -1;
		for (int i = 0; i < m_Codes.GetLength(); i++)
		{
			if (m_Codes[i] > m_CodeHighest) { m_CodeHighest = m_Codes[i]; }
		}
	}

	enum State
	{
		eState_None,
		eState_Command,
		eState_Group
	};

	enum Found
	{
		eFound_Lower,
		eFound_Upper,
		eFound_Latin,
		eFound_Digits,
		eFound_Punctuation
	};

	void AddCommand(tb::StringBase<wchar_t>& a_Target, Found a_Found)
	{
		switch (a_Found)
		{

		case eFound_Lower:
			{
				a_Target.Append(L"abcdefghijklmnopqrstuvwxyz");
				break;
			}

		case eFound_Upper:
			{
				a_Target.Append(L"ABCDEFGHIJKLMNOPQRSTUVWXYZ");
				break;
			}

		case eFound_Latin:
			{
				wchar_t str_latin[0x0100];

				wchar_t i = 0;
				wchar_t j = 0;

				for (i = 0x0041; i < 0x00FF; i++)
				{
					str_latin[j] = i;
					j++;
				}

				str_latin[j] = 0;

				a_Target.Append(str_latin);

				break;
			}

		case eFound_Digits:
			{
				a_Target.Append(L"0123456789");
				break;
			}

		case eFound_Punctuation:
			{
				a_Target.Append(L" -+=_()[]{}<>'\",.;:/\\|!?@#$%^&*`~");
				break;
			}
		}
	}

	void AddRange(tb::StringBase<wchar_t>& a_Target, wchar_t a_Start, wchar_t a_End)
	{
		for (wchar_t i = a_Start; i <= a_End; i++)
		{
			a_Target.Push(i);
		}
	}

	tb::StringBase<wchar_t> CharMap::Parse(const tb::StringBase<wchar_t>& a_Commands)
	{
		tb::StringBase<wchar_t> result;
		if (a_Commands.GetLength() == 0) { return result; }

		State cmd = eState_None;
		int cmd_start;
		tb::String cmd_string;
		for (int i = 0; i < a_Commands.GetLength();)
		{
			bool next = true;

			do
			{
				if (cmd == eState_None)
				{
					if (a_Commands[i] == ':')
					{
						cmd = eState_Command;
						cmd_start = i + 1;
						break;
					}
					else if (a_Commands[i] == '[')
					{
						cmd = eState_Group;
						cmd_start = i + 1;
						break;
					}

					result.Push(a_Commands[i]);
				}
				else if (cmd == eState_Command)
				{
					int cmd_end = a_Commands.FindNextLeft(':', cmd_start);
					if (cmd_end == -1) 
					{ 
						break;
					}
					cmd_string = a_Commands.GetSubMiddle(cmd_start, cmd_end - cmd_start);

					if (cmd_string == "default")
					{
						AddCommand(result, eFound_Upper);
						AddCommand(result, eFound_Lower);
						AddCommand(result, eFound_Digits);
						AddCommand(result, eFound_Punctuation);
					}
					else if (cmd_string == "lower")
					{
						AddCommand(result, eFound_Lower);
					}
					else if (cmd_string == "upper")
					{
						AddCommand(result, eFound_Upper);
					}
					else if (cmd_string == "latin")
					{
						AddCommand(result, eFound_Latin);
					}
					else if (cmd_string == "digits")
					{
						AddCommand(result, eFound_Digits);
					}
					else if (cmd_string == "punctuation")
					{
						AddCommand(result, eFound_Punctuation);
					}
					else
					{
						result.Push(L':');
						result.Append(cmd_string);
						result.Push(L':');
					}

					i += cmd_end - cmd_start + 1;
					next = false;

					cmd = eState_None;
				}
				else if (cmd == eState_Group)
				{
					int group_start = cmd_start;
					int group_end = a_Commands.FindNextLeft(']', cmd_start);
					if (group_end == -1) { break; }

					int cmd_dash = 0;
					while (1)
					{
						cmd_dash = a_Commands.FindNextLeft('-', cmd_start);
						if (cmd_dash == -1) { break; }
						if (cmd_dash + 1 >= group_end) { break; }
						cmd_start = cmd_dash + 1;

						wchar_t range_start = a_Commands[cmd_dash - 1];
						wchar_t range_end = a_Commands[cmd_dash + 1];

						if (range_end > range_start)
						{
							AddKnownRange(range_start, range_end);
							//AddRange(result, range_start, range_end);
						}
						else
						{
							AddKnownRange(range_end, range_start);
							//AddRange(result, range_end, range_start);
						}
					}

					i += group_end - group_start + 1;
					next = false;

					cmd = eState_None;
				}
			}
			while (0);

			if (next) { i++; }
		}

		return result;
	}

	int CharMap::GetCodeSize()
	{
		return m_Codes.GetLength();
	}

	int CharMap::GetCodeHighest()
	{
		return m_CodeHighest;
	}

	const tb::StringBase<wchar_t>& CharMap::GetCodes()
	{
		return m_Codes;
	}

	wchar_t CharMap::GetDefault()
	{
		return m_Default;
	}

	bool CharMap::GetInside(wchar_t a_Character)
	{
		unsigned int page = a_Character / 0xFF;
		if (!m_Pages[page]) { return false; }

		for (unsigned int i = 0; i < m_Pages[page]->chars_filled; i++)
		{
			if (m_Pages[page]->chars[i] == a_Character) { return true; }
		}

		return false;
	}

	void CharMap::_CompileCodes()
	{
		m_Codes.Clear();

		for (unsigned int j = 0; j < m_PagesTotal; j++)
		{
			if (!m_Pages[j]) { continue; }
			
			for (unsigned int i = 0; i < m_Pages[j]->chars_filled; i++)
			{
				m_Codes.Push(m_Pages[j]->chars[i]);
			}
		}
	}

	unsigned int CharMap::GetRangeStart()
	{
		return m_RangeStart;
	}

	unsigned int CharMap::GetRangeEnd()
	{
		return m_RangeEnd;
	}

	const std::vector<UnicodeRange*>& CharMap::GetUnicodeRanges()
	{
		return m_Ranges;
	}

	void CharMap::SetDefault(wchar_t a_Character)
	{
		if (a_Character == 0) 
		{ 
			m_Default = L'?'; 
		}
		else
		{
			m_Default = a_Character;
		}
	}

	void CharMap::_AddRange(UnicodeRange* a_Range)
	{
		if (a_Range->start < m_RangeStart) { m_RangeStart = a_Range->start; }
		if (a_Range->end > m_RangeEnd) { m_RangeEnd = a_Range->end; }
		m_Ranges.push_back(a_Range);
	}

	bool CharMap::GetRangeInside(std::vector<UnicodeRange*>& a_Ranges, unsigned int a_RangeStart, unsigned int a_RangeEnd)
	{
		//if (a_RangeEnd > m_RangeEnd) { return false; }

		unsigned int start = a_RangeStart;

		if (a_RangeStart < m_Ranges[0]->start) 
		{ 
			if (a_RangeEnd < m_Ranges[0]->start) { return false; }
			start = m_Ranges[0]->start;
		}

		bool finished = false;
		do 
		{
			for (size_t i = 0; i < m_Ranges.size(); i++)
			{
				if (start >= m_Ranges[i]->start)
				{
					if (a_RangeEnd > m_Ranges[i]->end)
					{
						UnicodeRange* result = new UnicodeRange;
						result->start = start;
						result->end = m_Ranges[i]->end;
						a_Ranges.push_back(result);

						if (i + 1 >= m_Ranges.size()) 
						{ 
							finished = true; 
							break;
						}

						start = m_Ranges[i + 1]->start;
					}
					else
					{
						UnicodeRange* result = new UnicodeRange;
						result->start = start;
						result->end = a_RangeEnd;
						a_Ranges.push_back(result);
						
						finished = true;
					}
				}
			}
		} 
		while (!finished);
		
		return true;
	}

	void CharMap::_AddBinRange(unsigned int a_Start, unsigned int a_End)
	{
		unsigned int bin_start = (a_Start / 32);
		unsigned int bin_end = (a_End / 32);

		if (!m_BinFirst)
		{
			m_BinFirst = new UnicodeBin;
			m_BinFirst->index = 0;
			m_BinFirst->next = NULL;
			m_BinLatest = m_BinFirst;

			m_BinAdded++;
		}

		for (unsigned i = bin_start; i <= bin_end; i++)
		{
			bool bin_insert = false;
			bool bin_equals = false;

			// search is O(n)

			UnicodeBin* bin_search = m_BinFirst;
			UnicodeBin* bin_search_prev = m_BinFirst;

			while (bin_search)
			{
				if (bin_search->index > i) 
				{ 
					bin_insert = true;
					break;
				}
				else if (bin_search->index == i)
				{
					bin_equals = true;
					break;
				}

				bin_search_prev = bin_search;
				bin_search = bin_search->next;
			}

			if (bin_equals) 
			{ 
				continue; 
			}
			else if (bin_insert)
			{
				UnicodeBin* bin_next = bin_search_prev->next;

				UnicodeBin* bin_next_insert = new UnicodeBin;
				bin_next_insert->index = i;
				bin_next_insert->next = bin_next;

				bin_search_prev->next = bin_next_insert;

				m_BinAdded++;
			}
			else
			{
				UnicodeBin* bin_next_add = new UnicodeBin;
				bin_next_add->index = i;
				bin_next_add->next = NULL;

				m_BinLatest->next = bin_next_add;
				m_BinLatest = bin_next_add;

				m_BinAdded++;
			}
		}
	}

}; // namespace Font